/*
** Copyright (C) 2009 Joshua Brent Marsh <joshua@icub3d.com>
**  
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 3 of the License, or
** (at your option) any later version.
**  
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**  
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**  
*/

/* $Id: utilities.c 53 2009-12-24 20:43:05Z joshua@icub3d.com $ */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <stdint.h>

#include <pthread.h>

#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

#include "istring.h"
#include "utilities.h"

/*******************************************************************************
 * Internal Declarartions.
 ******************************************************************************/
/* This is used to pass values to the thread functions. */
struct shell_args
{
  char *data;
  int * read_pipes;
  int * write_pipes;
};

/* Helpers for the load_config and load_configs functions. */
int load_config_helper (Map *m, char *file);
int add_line_to_map (Map *m, char *line);

/* These are the helper functions for execute_shell_command. They write/read
   from the pipe. We need to use threads because pipes can block if the buffer
   fills. */
void * write_to_pipe (void *params);
void * read_from_pipe (void *params);

/*******************************************************************************
 * Library Definitions.
 ******************************************************************************/
Map *
load_config (const char *file, const Map *def)
{
  Map *m;

  m = map_create ();
  if (m == NULL)
    return NULL;
  
  /* Put in the defaults. */
  if (def != NULL)
    {
      if (!map_union (m, def))
	{
	  int tmp_errno = errno;
	  map_destroy (m);
	  errno = tmp_errno;
	  return NULL;
	}
    }

  /* Load the configs from the file. */
  if (!load_config_helper (m, file))
    {
      int tmp_errno = errno;
      map_destroy (m);
      errno = tmp_errno;
      return NULL;
    }

  return m;
}

Map *
load_configs (char *dir, Map *def)
{
  Map *m, *n;

  struct stat s;
  struct dirent **entries;
  int dir_count = 0;
  int cur = 0;
  int failed = 0;

  char *path = NULL;
  
  m = map_create ();
  if (m == NULL)
    return NULL;

  /* Put in the defaults. */
  if (def != NULL)
    {
      if (!map_union (m, def))
	{
	  int tmp_errno = errno;
	  map_destroy (m);
	  errno = tmp_errno;
	  return NULL;
	}
    }

  /* Read the entries in alphabetical order. */
  dir_count = scandir (dir, &entries, NULL, alphasort);
  if (dir_count < 0)
    {
      int tmp_errno = errno;
      free (entries);
      errno = tmp_errno;
      return NULL;
    }

  for (cur = 0; cur < dir_count; cur++)
    {
      /* Skip the the links to cur and parent directories. */
      if (strcmp (entries[cur]->d_name, ".") == 0 ||
	  strcmp (entries[cur]->d_name, "..") == 0 ||
	  failed)
	{
	  free (entries[cur]);
	  continue;
	}

      /* Make the full path. */
      if (strdcat (&path, "/", 2, dir, entries[cur]->d_name) == 0)
	{
	  if (path != NULL)
	    free (path);

	  failed = 1;
	}

      if (path == NULL)
	{
	  failed = 1;
	}
      else
	{
	  /* Make sure the path exists. */
	  if (stat (path, &s) == -1)
	    failed = 1;
	  else
	    {
	      /* Check mode for the path. If it is we want to call
		 this recursively and join the maps together. */
	      if (S_ISDIR(s.st_mode))
		{
		  n = load_configs (path, NULL);
		  if (n == NULL)
		    failed = 1;
		  else 
		    {
		      if (!map_union (m, n))
			failed = 1;
		      
		      map_destroy (n);
		    }
		}
	      /* Load a single file. */
	      else if (!load_config_helper (m, path))
		failed = 1;
	    }

	  free (path);
	  path = NULL;
	}

      free (entries[cur]);
    }

  /* If we failed for some reason, clear the map, then exit. */
  if (failed)
    {
      int tmp_errno = errno;
      map_destroy (m);
      free (entries);
      errno = tmp_errno;
      return NULL;
    }

  free (entries);

  return m;
}

int
get_segment (char **pos, char **begin, char **end, const char *delims, 
	     const char *quotes, const char *nulls)
{
  int state = ISS_BEGIN;

  *begin = *pos;

  while (1)
    {
      /* The main section of our state machine. */
      switch (state)
	{
	case ISS_BEGIN:
	  /* If we reach the end of the line or another delim, we are
	     done. */
	  if (strchr (delims, **pos) != NULL || 
	      **pos == '\0' || 
	      strchr (nulls, **pos) != NULL)
	    {
	      *end = *pos;
	      state = ISS_END;
	    }
	  /* If we got a quote, we want to go until we get a quote. */
	  else if (strchr (quotes, **pos) != NULL)
	    {
	      *begin = *pos + 1;
	      state = ISS_QUOTED;
	    }
	  /* We are now in the segment. */
	  else
	    state = ISS_NOT_QUOTED;

	  break;

	case ISS_QUOTED:
	  /* If we reached the end, then the line isn't valid. */
	  if (**pos == '\0')
	    state = ISS_FAIL;
	  /* If we got a quote, we are done. */
	  else if (strchr (quotes, **pos) != NULL)
	    {
	      *end = *pos;
	      (*pos)++;
	      state = ISS_END;
	    }

	  break;

	case ISS_NOT_QUOTED:
	  /* If we got another delimiter or the end of the line, we
	     are done. */
	  if (strchr (delims, **pos) != NULL || 
	      **pos == '\0' || 
	      strchr (nulls, **pos) != NULL)
	    {
	      *end = *pos;
	      state = ISS_END;
	    }

	  break;
	}

      if (state == ISS_END)
	{
	  /* Move the current position past the delimiter if we are
	     done. */
	  if (**pos != '\0' && strchr (nulls, **pos) == NULL)
	    (*pos)++;

	  return ISS_END;
	}
      else if (state == ISS_FAIL)
	return ISS_FAIL;

      /* Move to the next character. */
      (*pos)++;

    }

  return ISS_FAIL;
}

size_t
uint8_t_numlen (const int8_t number, const size_t base)
{
  size_t count = 0;

  if (number <= 0)
    count++;

  while (number > 0)
    {
      number /= base;
      count++;
    }

  return count;
}

size_t
int8_t_numlen (const uint8_t number, const size_t base, 
	       const int include_negative)
{
  if (number < 0 && include_negative == 1)
    {
      number = -number;
      return int8_t_numlen (number, base) + 1;
    }
  else if (number < 0)
    {
      number = -number;
      return int8_t_numlen (number, base);
    }
  else
    return int8_t_numlen (number, base);
}

size_t
uint16_t_numlen (const int16_t number, const size_t base)
{
  size_t count = 0;

  if (number <= 0)
    count++;

  while (number > 0)
    {
      number /= base;
      count++;
    }

  return count;
}

size_t
int16_t_numlen (const uint16_t number, const size_t base, 
		const int include_negative)
{
  if (number < 0 && include_negative == 1)
    {
      number = -number;
      return int16_t_numlen (number, base) + 1;
    }
  else if (number < 0)
    {
      number = -number;
      return int16_t_numlen (number, base);
    }
  else
    return int16_t_numlen (number, base);
}

size_t
uint32_t_numlen (const int32_t number, const size_t base)
{
  size_t count = 0;

  if (number <= 0)
    count++;

  while (number > 0)
    {
      number /= base;
      count++;
    }

  return count;
}

size_t
int32_t_numlen (const uint32_t number, const size_t base, 
		const int include_negative)
{
  if (number < 0 && include_negative == 1)
    {
      number = -number;
      return int32_t_numlen (number, base) + 1;
    }
  else if (number < 0)
    {
      number = -number;
      return int32_t_numlen (number, base);
    }
  else
    return int32_t_numlen (number, base);
}

size_t
uint64_t_numlen (const int64_t number, const size_t base)
{
  size_t count = 0;

  if (number <= 0)
    count++;

  while (number > 0)
    {
      number /= base;
      count++;
    }

  return count;
}

size_t
int64_t_numlen (const uint64_t number, const size_t base, 
		const int include_negative)
{
  if (number < 0 && include_negative == 1)
    {
      number = -number;
      return int64_t_numlen (number, base) + 1;
    }
  else if (number < 0)
    {
      number = -number;
      return int64_t_numlen (number, base);
    }
  else
    return int64_t_numlen (number, base);
}

size_t
snumlen (const size_t number, const size_t base)
{
  size_t count = 0;

  if (number <= 0)
    count++;

  while (number > 0)
    {
      number /= base;
      count++;
    }

  return count;
}

size_t
ssnumlen (const ssize_t number, const size_t base, 
	  const int include_negative)
{
  if (number < 0 && include_negative == 1)
    {
      number = -number;
      return snumlen (number, base) + 1;
    }
  else if (number < 0)
    {
      number = -number;
      return snumlen (number, base);
    }
  else
    return snumlen (number, base);
}

int
mkrdir (const char *pathname, const mode_t mode)
{
  char *path = NULL;
  size_t len, x;
  char *cur;

  path = malloc (strlen (pathname) + 1);
  if (path == NULL)
    return -1;

  strcpy (path, pathname);

  len = strlen (path);
  
  /* We don't need to create the final slashes. */
  for (x = len; len > 0; len--)
    {
      if (path[x - 1] == '/')
	path[x - 1] = '\0';
      else
	break;
    }

  cur = path;

  /* Skip the first slash if it's there. */
  if (*cur == '/')
    cur++;

  for (; *cur != '\0'; cur++)
    {
      /* If we find a slash, we cshould make the dir for it. */
      if (*cur == '/')
	{
	  *cur = '\0';
	  if (mkdir (path, mode) == -1)
	    {
	      if (errno != EEXIST)
		{
		  int tmp_errno = errno;
		  free (path);
		  errno = tmp_errno;
		  return -1;
		}
	    }
	  *cur = '/';
	}
    }

  /* Do the last one. */
  if (mkdir (path, mode) == -1)
    {
      int tmp_errno = errno;
      free (path);
      errno = tmp_errno;

      if (errno != EEXIST)
	return -1;
      else 
	return 0;
    }
  else
    {
      free (path);
      return 0;
    }
}

int
check_file_exists (const char *path)
{
  struct stat st;

  if (stat (path, &st) == -1)
    return 0;

  if (!S_ISREG(st.st_mode))
    return 0;

  return 1;
}

int
write_file (const char *file_path, const char *data)
{
  FILE *out;
  size_t written = 0, total = 0;
  size_t to_write = strlen (data);

  out = fopen (file_path, "w");
  if (out == NULL)
    return 0;

  while ((written = fwrite (data + written, 1, to_write - written, out))
	 != -1)
    {
      total += written;

      if (total == to_write)
	break;
    }

  if (written == -1)
    {
      int tmp_errno = errno;
      fclose (out);
      errno = tmp_errno;
      return 0;
    }
  
  fclose (out);

  return 1;
}


char *
read_file (const char *file_path)
{
  FILE *in;
  size_t read = 0;
  ssize_t response;
  char *ret = NULL, *line = NULL;

  in = fopen (file_path, "r");
  if (in == NULL)
    return NULL;

  while ((response = getline (&line, &read, in)) != -1)
    {
      if (strmcat (&ret, line) == -1)
	{
	  int tmp_errno = errno;
	  if (ret != NULL)
	    free (ret);

	  if (line != NULL)
	    free (line);
	  
	  fclose (in);

	  errno = tmp_errno;
	  return NULL;
	}
    }

  /* Clean up on error. */
  if (response == -1)
    {
      int tmp_errno = errno;

      if (ret != NULL)
	free (ret);

      if (line != NULL)
	free (line);

      fclose (in);
      errno = tmp_errno;
      return NULL;
    }

  if (line != NULL)
    free (line);
	  
  fclose (in);

  return ret;
}

char *
execute_shell_command (const char *command, const char *args[], const 
		       char *data)
{
  char *response = NULL;

  pid_t pid;
  int read_pipes[2];
  int write_pipes[2];

  pthread_t read_thread, write_thread;
  struct shell_args read_args, write_args;

  /* Open the two pipes for communication. */
  if (pipe (read_pipes) == -1)
    return NULL;

  if (pipe (write_pipes) == -1)
    {
      int tmp_errno = errno;
      close (write_pipes[0]);
      close (write_pipes[1]);
      errno = tmp_errno;
      return NULL;
    }

  /* Create the child process for the execvp. */
  pid = fork();
  if (pid == 0) /* Child. */
    {
      /* We don't need these sides in the child. */
      close (write_pipes[1]);
      close (read_pipes[0]);

      /* link to stdin and stdout. */
      dup2 (write_pipes[0], STDIN_FILENO);
      dup2 (read_pipes[1], STDOUT_FILENO);

      /* Don't need these anymore. */
      close (write_pipes[0]);
      close (read_pipes[1]);

      execvp (command, args);

      return NULL;
    }
  else if (pid < 0) /* This means the fork failed! */
    {
      close (write_pipes[0]);
      close (write_pipes[1]);

      close (read_pipes[0]);
      close (read_pipes[1]);

      return NULL;
    }
  else /* Parent. */
    {
      /*      struct sigaction nact, oact;
      nact.sa_handler = SIG_IGN;
      sigemptyset(&nact.sa_mask);
      nact.sa_flags = 0;
      sigaction (SIGPIPE, &nact, &oact);
      */
      /* We don't need these sides for the parent. */
      close (write_pipes[0]);
      close (read_pipes[1]);

      write_args.data = data;
      write_args.read_pipes = read_pipes;
      write_args.write_pipes = write_pipes;

      read_args.data = data;
      read_args.read_pipes = read_pipes;
      read_args.write_pipes = write_pipes;

      pthread_create (&read_thread, NULL, &read_from_pipe, &read_args);
      pthread_create (&write_thread, NULL, &write_to_pipe, &write_args);


      pthread_join (write_thread, NULL);
      pthread_join (read_thread, (void *) &response);

      return response;

    }
}


int
send_email (char *program, char *to, char *from, 
	    char *cc, char *subject, char *message, ...)
{
  va_list args;

  if (program == NULL)
      return 0;

  /* Open up a pipe to sendmail to write to. */
  FILE *email = popen (program, "w");
  if (email == NULL)
    return 0;

  /* Output the e-mail to the sendmail program. */
  fprintf (email, "To: %s\r\n", to);
  fprintf (email, "From: %s\r\n", from);
  fprintf (email, "CC: %s\r\n", cc);
  fprintf (email, "Subject: %s\r\n", subject);
  fprintf (email, "\r\n");

  va_start (args, message);
  vfprintf (email, message, args);
  va_end (args);

  pclose (email);

  return 1;
}

/*******************************************************************************
 * Internal Definitions
 ******************************************************************************/
int
load_config_helper (Map *m, char *file)
{
  struct stat s;
  FILE *fd;
  char *line = NULL;
  ssize_t read;
  size_t n = 0;

  if (stat (file, &s) == -1)
    return 0;

  if (!S_ISREG(s.st_mode))
    return 0;

  fd = fopen (file, "r");
  if (fd == NULL)
    return 0;

  while ((read = getline (&line, &n, fd)) != -1)
    {
      if (!add_line_to_map (m, line))
	{
	  if (line != NULL)
	    free (line);
	  
	  fclose (fd);

	  return 0;
	}
    }

  if (line != NULL)
    free (line);

  fclose (fd);

  return 1;
}

int
add_line_to_map (Map *m, char *line)
{
  char *cur;
  char *begin_name = NULL;
  char *end_name = NULL;
  char *begin_value = NULL;
  char *end_value = NULL;

  char temp_name, temp_value;
  int estate;

  cur = line;
  while (isspace (*cur))
    cur++;

  if (*cur == '\0' || *cur == '#')
    return 1;

  estate = get_segment(&cur, &begin_name, &end_name, " =\t\r\n", "\"", "#");
  if (estate == ISS_FAIL)
    {
      return 0;
    }
  
  while (isspace (*cur) || *cur == '=')
    cur++;

  if (*cur == '\0')
    return 0;

  estate = get_segment(&cur, &begin_value, &end_value, " \t\r\n", "\"", "#");
  if (estate == ISS_FAIL)
    {
      return 0;
    }

  temp_name = *end_name;
  temp_value = *end_value;
  *end_name = '\0';
  *end_value = '\0';

  if (!map_insert (m, begin_name, begin_value))
    {
      *end_name = temp_name;
      *end_value = temp_value;

      return 0;
    }

  *end_name = temp_name;
  *end_value = temp_value;
  
  return 1;
}

void *
write_to_pipe (void *params)
{
  struct shell_args *args = params;
  ssize_t written = 0, count;

  /* If we aren't writing anything, don't try. We could get a SIGPIPE. */
  if (args->data == NULL)
    {
      close (args->write_pipes[1]);
      return NULL;
    }
  else if (strlen(args->data) == 0)
    {
      close (args->write_pipes[1]);
      return NULL;
    }

  /* write the file to "stdin". */
  while (1)
    {
      count = write (args->write_pipes[1], args->data + written, 
		     strlen (args->data) - written);

      if (count == -1)
	break;

      written += count;

      if (written == strlen (args->data))
	break;
    }
  close (args->write_pipes[1]);

  return NULL;
}

void *
read_from_pipe (void *params)
{
  struct shell_args *args = params;

  char *line = NULL;
  char *response = NULL;
  size_t len = 0;
  FILE * pd;

  pd = fdopen (args->read_pipes[0], "r");
  if (pd == NULL)
    {
      close (args->read_pipes[0]);
      fprintf (stderr, "I Died!\n");
      return NULL;
    }

  /* Read each line of the output and append it to the response. */
  while (getline (&line, &len, pd) != -1)
    {
      if (strmcat (&response, line) == -1)
	{
	  /* Something bad happened. */
	  free (response);
	  response = NULL;
	  break;
	}
    }
      
  fclose (pd);
  close (args->read_pipes[0]);
  free (line);

  return (void *) response;
}
